<script setup>
import SparkMD5 from 'spark-md5';
import FileUploadService from "@/service/impl/FileUploadService.js";
import {nextTick, onMounted, ref} from "vue";

const message = inject('message')
const props = defineProps({
    setting: {
        type: Object,
        default: () => {
            return {
                size: 1, // 控制每个块大小 MB
                concurrent: 3, // 并发数量控制
                info: false, // 是否显示上传详情
            }
        }
    }
})

const uploadInfo = ref({
    selectedFile: null,
    fileHash: '',
    totalChunks: 0,
    chunkSize: 0, // 1MB
    uploadedChunks: 0,
    progress: 0,
    uploading: false,
    uploadSpeed: 0,
    estimatedTime: 0,
    uploadResult: null,
    startTime: 0
})
const emits = defineEmits([
    'onFinish',// 页数改变事件
])

const fileInput = ref(null)

const uploadInfoInit = () => {
    nextTick(() => uploadInfo.value = {
        selectedFile: null,
        fileHash: '',
        totalChunks: 0,
        chunkSize: (1024 * 1024) * props.setting.size, // 1MB
        uploadedChunks: 0,
        progress: 0,
        uploading: false,
        uploadSpeed: 0,
        estimatedTime: 0,
        uploadResult: null,
        startTime: 0
    })
}
const onUpload = () => {
    uploadInfoInit()
    fileInput.value?.click();
}
const handleFileSelect = async (event) => {
    uploadInfo.value.selectedFile = await event.target.files[0];
    uploadInfo.value.uploadResult = null;
    await startUpload()
}
const calculateFileHash = async (file) => {
    return new Promise((resolve) => {
        const chunkSize = 2097152; // 2MB
        const spark = new SparkMD5.ArrayBuffer();
        const fileReader = new FileReader();
        let cursor = 0;

        const processChunk = () => {
            const chunk = file.slice(cursor, cursor + chunkSize);
            fileReader.readAsArrayBuffer(chunk);
        };

        fileReader.onload = (e) => {
            spark.append(e.target.result);
            cursor += chunkSize;

            if (cursor < file.size) {
                processChunk();
            } else {
                resolve(spark.end());
            }
        };
        fileReader.onerror = () => {
            console.error('文件读取错误');
            resolve('');
        };
        processChunk();
    });
}

// 提取并发控制为通用方法
const concurrentControl = async (tasks, concurrency) => {
    const results = [];
    const executing = [];
    const enqueueTask = async (task) => {
        const promise = task().then(result => {
            results.push(result);
            // 移除已完成的任务
            executing.splice(executing.indexOf(promise), 1);
            return result;
        });
        executing.push(promise);
        // 如果超过并发数，则等待一个任务完成后再继续
        if (executing.length >= concurrency) {
            await Promise.race(executing);
        }
    };
    for (const task of tasks) {
        await enqueueTask(task);
    }
    // 等待所有任务完成
    await Promise.all(executing);
    return results;
}

const startUpload = async () => {
    if (!uploadInfo.value.selectedFile) return;

    uploadInfo.value.uploading = true;
    uploadInfo.value.uploadedChunks = 0;
    uploadInfo.value.progress = 0;
    uploadInfo.value.startTime = Date.now();

    try {
        // 计算文件哈希
        uploadInfo.value.fileHash = await calculateFileHash(uploadInfo.value.selectedFile);
        console.log('文件哈希:', uploadInfo.value.fileHash)
        // 获取已上传的块
        const existingChunks = await checkExistingChunks();
        // 准备分块上传
        uploadInfo.value.totalChunks = Math.ceil(uploadInfo.value.selectedFile.size / uploadInfo.value.chunkSize);

        // 并行上传块
        const tasks = [];
        console.log('准备上传块的数量:', uploadInfo.value.totalChunks)

        for (let i = 0; i < uploadInfo.value.totalChunks; i++) {
            // 是否已经上传过
            if (!existingChunks.includes(i)) {
                tasks.push(() => uploadChunk(i));
            } else {
                // 更新进度
                uploadInfo.value.uploadedChunks++;
                updateProgress();
            }
        }

        // 使用并发控制上传
        await concurrentControl(tasks, props.setting.concurrent); // 设置最大并发数为5

        // 合并文件
        uploadInfo.value.uploadResult = await mergeChunks();
        // 清空input文件
        fileInput.value.value = '';
        emits('onFinish', uploadInfo.value.uploadResult)
    } catch (error) {
        message.warning('上传失败');
    } finally {
        uploadInfo.value.uploading = false;
    }
}
const checkExistingChunks = async () => {
    const response = await FileUploadService.exists({fileHash: uploadInfo.value.fileHash})
    if(await response.code !== 200){
        return [];
    }
    return await response.data;
}
const uploadChunk = async (index) => {
    const start = index * uploadInfo.value.chunkSize;
    const end = Math.min(start + uploadInfo.value.chunkSize, uploadInfo.value.selectedFile.size);
    const chunk = uploadInfo.value.selectedFile.slice(start, end);
    const startTime = Date.now();
    try {
        const response = await FileUploadService.chunk({
            file: chunk,
            fileHash: uploadInfo.value.fileHash,
            fileName: uploadInfo.value.selectedFile.name,
            chunkIndex: index,
            totalChunks: uploadInfo.value.totalChunks,
        });
        if(await response.code !== 200){
            return;
        }
        const elapsedTime = (Date.now() - startTime) / 1000;
        const chunkSizeKB = chunk.size / 1024;
        uploadInfo.value.uploadSpeed = chunkSizeKB / elapsedTime;
        uploadInfo.value.uploadedChunks++;
        updateProgress();
    } catch (error) {
        console.error(`上传块 ${index} 失败:`, error);
        throw error;
    }
}
const mergeChunks = async () => {
    try {
        const response = await FileUploadService.merge( {
            fileHash: uploadInfo.value.fileHash,
            fileName: uploadInfo.value.selectedFile.name,
            totalChunks: uploadInfo.value.totalChunks,
            path: "test"
        });
        if(response.code !== 200){
            return;
        }
        return await response.data;
    } catch (error) {
        console.error('合并文件失败:', error);
        throw new Error('合并文件失败: ' + error.message);
    }
}
const updateProgress = () => {
    uploadInfo.value.progress = (uploadInfo.value.uploadedChunks / uploadInfo.value.totalChunks) * 100;
    const elapsedTime = (Date.now() - uploadInfo.value.startTime) / 1000;
    const remainingChunks = uploadInfo.value.totalChunks - uploadInfo.value.uploadedChunks;
    if (uploadInfo.value.uploadSpeed > 0 && remainingChunks > 0) {
        const avgSpeed = (uploadInfo.value.selectedFile.size / 1024) / elapsedTime;
        const remainingSize = (remainingChunks * uploadInfo.value.chunkSize) / 1024;
        uploadInfo.value.estimatedTime = Math.round(remainingSize / avgSpeed);
    }
}

onMounted(() => {
    uploadInfoInit()
})
</script>

<template>
    <el-button type="primary" @click="onUpload">
        {{uploadInfo.uploading ? '正在解析上传...' : '选择文件上传'}}
        {{uploadInfo.progress && uploadInfo.progress > 0? Math.floor(uploadInfo.progress) + '%' : ''}}
        <input type="file" @change="handleFileSelect" ref="fileInput" style="display: none">
    </el-button>
    <el-row v-if="setting.info">
        <el-tag type="primary" size="small" disable-transitions>
            已上传 {{ uploadInfo.uploadedChunks }}/{{ uploadInfo.totalChunks }} 块，
            速度: {{ Math.floor(uploadInfo.uploadSpeed) }} KB/s，
            剩余时间: {{ Math.floor(uploadInfo.estimatedTime) }} 秒
        </el-tag>
    </el-row>
</template>

<style lang="scss" scoped>


</style>